package com.zhss.dfs.namenode.server;

import java.util.LinkedList;

/**
 * 负责管理edits log日志的核心组件
 */
public class FSEditLog {
    /**
     * 当前递增到的txid的序号
     */
    private long txidSeg = 0L;
    //内存双缓冲区
    private DoubleBuffer editLogBuffer = new DoubleBuffer();

    private volatile Boolean isSyncRunning = false;
    /**
     * 每个线程自己本地的txid副本
     */
    private ThreadLocal<Long> localTxid = new ThreadLocal<Long>();
    /**
     * 在同步到磁盘中最大的一个txid
     */
    private volatile Long syncMaxTxid = 0L;
    /**
     * 当前是否有线程在等待刷新下一批edits log到磁盘里去
     */
    private volatile Boolean isWaitSync = false;
    /**
     * 记录editslog日志
     * @param content
     */
    public void logEdit(String content){
        //
        synchronized (this){
            //获取全局唯一递增的txid，代表了editslog序号
            txidSeg++;
            long txid = txidSeg;
            localTxid.set(txid);
            EditLog editLog = new EditLog(txid,content);
            editLogBuffer.write(editLog);
        }
        logSync();
    }

    /**
     * 将内存缓冲中的数据刷入磁盘文件中
     * 尝试运行某一线程将内存缓冲区的数据刷入磁盘文件中
     * 实现批量将内存缓冲数据刷磁盘的过程
     */
    private void logSync() {
        synchronized (this){
            //如果当前有线程在刷内存缓冲到磁盘
            if(isSyncRunning){
                // 假如说某个线程已经把txid = 1,2,3,4,5的edits log都从syncBuffer刷入磁盘了
                // 或者说此时正在刷入磁盘中
                // 此时syncMaxTxid = 5，代表的是正在输入磁盘的最大txid
                // 那么这个时候来一个线程，他对应的txid = 3，此时他是可以直接返回了
                // 就代表说肯定是他对应的edits log已经被别的线程在刷入磁盘了
                // 这个时候txid = 3的线程就不需要等待了
                long txid = localTxid.get();
                if(txid <= syncMaxTxid){
                    return;
                }

                // 此时再来一个txid = 9的线程的话，那么他会发现说，已经有线程在等待刷下一批数据到磁盘了
                // 此时他会直接返回
                // 假如说此时来一个txid = 6的线程，那么的话，他是不好说的
                // 他就需要做一些等待，同时要释放掉锁
                if(isWaitSync){
                    return;
                }
                isWaitSync = true;
                while (isSyncRunning){
                    try {
                        wait(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                isWaitSync = false;
            }
            //交换两块缓冲区
            editLogBuffer.setReadyToSync();
            // 然后可以保存一下当前要同步到磁盘中去的最大的txid
            // 此时editLogBuffer中的syncBuffer这块区域，交换完以后这里可能有多条数据
            // 而且他里面的edits log的txid一定是从小到大的
            // 此时要同步的txid = 6,7,8,9,10,11,12
            // syncMaxTxid = 12
            syncMaxTxid = editLogBuffer.getSyncMaxTxid();
            // 设置当前正在同步到磁盘的标志位
            isSyncRunning = true;
        }

        // 开始同步内存缓冲的数据到磁盘文件里去
        // 这个过程其实是比较慢，基本上肯定是毫秒级了，弄不好就要几十毫秒
        editLogBuffer.flush();

        synchronized (this){
            // 同步完了磁盘之后，就会将标志位复位，再释放锁
            isSyncRunning = false;
            // 唤醒可能正在等待他同步完磁盘的线程
            notifyAll();

        }
    }

    /**
     * 代表一条 edits  log
     */
    class EditLog {
        private long txid;
        private String content;

        public EditLog(long txid, String content) {
            this.txid = txid;
            this.content = content;
        }
    }

    /**
     * 内存双缓冲
     */
    class DoubleBuffer{
        /**
         * 专门用来承载线程写入editslog
         */
        private LinkedList<EditLog> currentBuffer = new LinkedList();
        /**
         * 专门用来将数据同步到磁盘中的一块缓冲
         */
        private LinkedList<EditLog> syncBuffer = new LinkedList();

        /**
         * 将editslog 写入内存缓冲里去
         * @param editLog
         */
        public void write(EditLog editLog){
            currentBuffer.add(editLog);
        }

        /**
         * 交换两块缓冲区，为了同步内存数据到磁盘做准备
         */
        public void setReadyToSync(){
            LinkedList<EditLog> tmp = currentBuffer;
            currentBuffer = syncBuffer;
            syncBuffer = tmp;
        }

        /**
         * 将syncBuffer缓冲区中的数据刷到磁盘中
         */
        public void flush(){
            for (EditLog log : syncBuffer) {
                System.out.println("将edit log写入磁盘文件中："+log);
                //此处应该用文件输出流将数据写入磁盘文件中
            }
            syncBuffer.clear();
        }

        /**
         * 获取syncBuffer中最大的txid
         * @return
         */
        public Long getSyncMaxTxid(){
            return syncBuffer.getLast().txid;
        }
    }
}
